/* 
 * File:   Sesion.cpp
 * Author: Maximiliano Monzon (82341)
 * 
 * Created on October 18, 2012, 7:00 PM
 */

#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "server_Sesion.h"
#include "common_Thread.h"
#include "common_ProtocoloConstantes.h"

using namespace TP::Threading;

using namespace std;
using namespace TP::Comunicacion;

#define CONEXION_FINALIZADA 0
#define CONEXION_ERROR -1
#define BUFFER_SIZE 256

Sesion::Sesion(int pFileDescriptor, struct sockaddr_in* pClientAddress){
    
    this->fileDescriptor = pFileDescriptor;
    this->clientAddress = pClientAddress;
    this->timeout = 0;
}

Sesion::Sesion(int pFileDescriptor, struct sockaddr_in* pClientAddress, const int &pTimeout){

    this->fileDescriptor = pFileDescriptor;
    this->clientAddress = pClientAddress;
    
    if (pTimeout > 0)
        this->timeout = pTimeout;
    else
        this->timeout = 0;
}

Sesion::~Sesion(){
    if (this->clientAddress)
        delete this->clientAddress;
}

void Sesion::agregarMensajeEntranteManejador(IMensajeEntranteManejador* pManejador){
    if (pManejador)
        this->mensajeSesionManejadores.push_back(pManejador);
}

void Sesion::agregarManejadorSesionEvento (IManejadorSesionEvento* pManejador){
    if (pManejador)
        this->manejadorSesionEvento = pManejador;
}

void Sesion::Iniciar() throw (Excepcion&){
    
    try
    {
        this->conexionFinalizada = false;
        
        // Inicio la tarea de la sesion.
        Thread::Iniciar();
    }
    catch (Excepcion& pEx)
    {
        throw;
    }

}

void Sesion::Finalizar() throw (Excepcion&){
    try
    {
        // Cierro la conexion en caso de que se encuentre activa.        
        if (!this->conexionFinalizada)
        {
            this->conexionFinalizada = true;
            shutdown(this->fileDescriptor, SHUT_RDWR );
        }
        
        // Finalizo las operaciones.
        Thread::Finalizar();
    }
    catch (Excepcion& pEx)
    {
        throw;
    }
}

void Sesion::Responder (const string& pMensaje) throw (Excepcion&) {
    
    try
    {
        /* Toma el mensaje, y lo escribe a traves de la conexion.
         */
        string msg_env = pMensaje;
        
        // Finalizo el mensaje para transmision.
        msg_env += FIN_MENSAJE;

        // Envio el mensaje.
        
        int intentos = this->timeout;
        int cntBytes = 0;
        int byteIndex = 0;
        
        while (intentos > 0)
        {
            cntBytes = send (this->fileDescriptor, &(msg_env.c_str()[byteIndex]), msg_env.size()- byteIndex, 0);
            
            if (cntBytes > 0)
            {
                byteIndex += cntBytes;
                
                if (msg_env.length() == byteIndex)
                    break;
                else
                    intentos--;
            }
            else
            {
                // Hubo un error en la comunicacion.
                
                if (0 == cntBytes)
                    throw Excepcion ("Perdida de conexion.");
                else
                    throw Excepcion ("Fallo de comunicacion.");
            }
        }
        
        if (intentos == 0)
            throw Excepcion ("Tiempo de conexion acabado.");
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede escribir el mensaje.");
        throw;
    }    
    
}

void Sesion::tarea(){
    
    /* La sesion tiene como tarea recibir los mensajes entrantes, desencodearlos
     * y notificar el mensaje a sus suscriptores.
     * 
     * La tarea es valida siempre y cuando la conexion no haya sido finaliada
     * por el host.
     */
    try
    {
        char buffer [BUFFER_SIZE];
        int bytes_leidos = -1;
        int intentos = this->timeout;
        string mensaje = "";
        
        while (true)
        {
            // Inicializo el buffer para lectura.
            bzero(buffer,BUFFER_SIZE);
            
            bytes_leidos = recv (this->fileDescriptor, buffer, BUFFER_SIZE, 0);

            if (bytes_leidos <= 0) 
                throw bytes_leidos; /*Error de comunicacion.*/
            
            for (int i = 0; i < bytes_leidos; i++)
            {
                if (FIN_MENSAJE == buffer [i])
                {
                    this->notificarMensaje(mensaje);
                    mensaje = "";
                }
                else
                    mensaje += (char) buffer [i];
            }
        }        
    }
    catch (int ex)
    {
        this->manejarErrorComunicacion(ex); 
    }
}

string& Sesion::notificarMensaje (const string& pMensaje){

    // Notifico el mensaje a los servicios.
    for (vector<IMensajeEntranteManejador*>::iterator it = this->mensajeSesionManejadores.begin(); it != this->mensajeSesionManejadores.end(); ++it)
        (*it)->mensajeEntranteManejador((IMensajero*) this, pMensaje);                

}


void Sesion::manejarErrorComunicacion (int pEstadoComunicacion){

    if (CONEXION_FINALIZADA == pEstadoComunicacion)
    {
        // Cierro el descriptor.
        close (this->fileDescriptor);
    }
    else if (CONEXION_ERROR == pEstadoComunicacion)
    {
        string msg = "Error de lectura del host ";
        msg += inet_ntoa(this->clientAddress->sin_addr);
        msg += ".";

        this->manejadorSesionEvento->eventoErrorComunicacion((IEnviante*)this, msg);
    }
}

